#!/usr/bin/env python2.6
"""
Process lib.rus.ec archive files (.zip files witg fb2 files inside) and updates the local collection.

File database.db is required to run this script.
"""
from pysqlite2 import dbapi2 as sqlite
from sys import stderr
from sys import exit
import os
import re
import glob
import zipfile
import sys

# change these two variables
## directory with lib.rus.ec archives (i.e. with the files like 109377-110395.zip)
librusec_archives_path = "/home/cancel/lib.rus.ec/archives"
## directory with the local collection of books
collection_path = "/home/cancel/lib.rus.ec/collection"

def make_book_path(book_id):
    """
    Make book path for given book_id, book path is something like (3, 9) for the book_id 65493
    """
    s1 = book_id % 10
    s2 = (book_id / 10) % 10
    
    return (s1, s2)
    

# create temporary table
db = sqlite.connect("database.db")
cursor = db.cursor()

# read all files from the directory librusec_archives_path
files = glob.glob(os.path.join(librusec_archives_path, "*.zip"))
#file_pattern_re = re.compile('^[0-9]+-[0-9]+\.zip$')
file_fb2_pattern = re.compile("^(.*)\.fb2$")
file_id_pattern_re = re.compile('^([0-9]+)$')

# list of updated books ids
books_updated = []
total_archives = len(files)
i = 0

for filename in files:
    i += 1
    print "processing file %d of %d `%s'" % (i, total_archives, filename)
    #if file_pattern_re.match(os.path.basename(filename)) is None:
    #    print "File is not lib.rus.ec archive, skipping"
    #    continue
    
    try:
        zz = zipfile.ZipFile(filename, "r", allowZip64=True)
        namelist = zz.namelist()
        for f in namelist:
            mo = file_fb2_pattern.match(unicode(f, "UTF-8"))
            if mo is None:
                continue
            basename = mo.group(1)
            
            # find book id that corresponds to filename
            book_id = None
            mo = file_id_pattern_re.match(basename)
            if mo is not None:
                book_id = int(mo.group(1))
            else:
                print "Failed to process book with filename '%s'" % basename
                sys.exit()
            
            # Check is book replaced by newer version
            query = "SELECT COUNT(*) FROM libjoinedbooks WHERE BadId=?"
            cursor.execute(query, [book_id])
            row = cursor.fetchone()
            if row[0] != 0:
                # book is outdated and should not be updated
                books_updated.append(book_id)
            else:
                # extract book from the archive and put to appropriate place 
                # in the local collection tree
                book_path = [str(x) for x in make_book_path(book_id)]
                target_dir = os.path.join(collection_path, *book_path)
                if os.path.exists(target_dir) and not os.path.isdir(target_dir):
                    print >>stderr, "Fatal error, path `%s' must be directory!" % target_dir
                    exit(1)
                
                if not os.path.exists(target_dir):
                    # try to create dirs tree
                    try:
                        os.makedirs(target_dir, 0755)
                    except error:
                        print >>stderr, "Fatal error, path `%s' colud not be created!" % target_dir
                        exit(1)
                
                target_zip_filename = os.path.join(target_dir, "%d.fb2.zip" % book_id)
                if os.path.exists(target_zip_filename):
                    # skip file if it's already exists
                    continue
                
                content = zz.read(f)
                tz = zipfile.ZipFile(target_zip_filename, "w", zipfile.ZIP_DEFLATED) # allowZip64=True
                try:
                    tz.writestr(f, content) # ZIP don't understand unicode names
                except UnicodeDecodeError, e:
                    print >>stderr, "Unable to add file with name `%s' into the zip archive: %s" % (f, str(e))
                    
                tz.close()
            
        zz.close()
            
    except zipfile.BadZipfile, e:
        print >>stderr, "Error: unable to process file `%s': %s" % (filename, e.message)
        continue
    except IOError, e:
        print >>stderr, "Error: unable to process file `%s', IO Error: %s" % (filename, e.message)
        continue
    
    #break

# now process deleted books
query = "SELECT BookId FROM libbook WHERE Deleted=1"
cursor.execute(query)
books_deleted = [x for x, in cursor]
#print books_deleted

books_updated += books_deleted

# now process list of updated books
# all these books should be deleted
for book_id in books_updated:
    book_path = [str(x) for x in make_book_path(book_id)]
    target_dir = os.path.join(collection_path, *book_path)
    target_zip_filename = os.path.join(target_dir, "%d.fb2.zip" % book_id)
    if os.path.isfile(target_zip_filename):
        try:
            os.unlink(target_zip_filename)
        except OSError, e:
             print >>stderr, "Unable to remove updated file `%s': %s" (target_zip_filename, e.message)
